/**
 * 
 */
package stubs;

import interfaces.IMeasure;
import interfaces.IRange;

import java.util.ArrayList;

import cern.jet.random.Normal;

import massDistribution.ContinueMeasure;
import massDistribution.DiscreteMeasure;
import massDistribution.DiscreteRange;
import massDistribution.MeasuredAttribute;
import massDistribution.Metric;
import massDistribution.MetricType;
import engine.ExternalSource;
import engine.Service;
import exceptions.NormalValueGenerationException;

/**
 * @author Elisa Costante
 * 
 */
@SuppressWarnings("unchecked")
public class AttributeValueGenerator {

	/**
	 * The percentage of deviation from mean
	 */
	private static final double STANDARD_DEVIATION_PERCENTAGE = 0.2;

	/**
	 * Genarate an {@link IMeasure} for the attribute's metric based on the
	 * Gaussian ProbabilityDistribution and the {@link MetricType} of the
	 * {@link ServiceStub}.
	 * 
	 * @param service
	 * @param attribute
	 * @return
	 * @throws NormalValueGenerationException
	 */
	@SuppressWarnings("unchecked")
	public static IMeasure getGaussianMeasure(ServiceStub service,
			MeasuredAttribute attribute) throws NormalValueGenerationException {

		DiscreteMeasure[] discreteElements = null;
		Double mean = null;
		double stdDev = 0;

		if ((attribute.getMetric().getType()).equals(MetricType.INTEGER)
				|| attribute.getMetric().getType().equals(MetricType.REAL)) {

			mean = getMeanContinuosCase(attribute, service.getTrustValue());

			if (mean != null) {
				// Generate a continuous Measure
				stdDev = setStandardDeviation(mean);
				Comparable value = generateContinuousValue(mean, stdDev,
						attribute.getMetric());
				return new ContinueMeasure(value);
			} else
				throw new NormalValueGenerationException(
						"The generated value is not valid");

		} else if (attribute.getMetric().getType().equals(MetricType.DISCRETE)) {

			int numberOfElement = attribute.getMetric().getElementsSize();
			discreteElements = new DiscreteMeasure[numberOfElement];
			// Set the array of valid Discrete Measures
			int i = 0;
			for (IRange range : attribute.getMetric().getValidRanges()) {
				DiscreteRange discreteRange = (DiscreteRange) range;
				ArrayList<DiscreteMeasure> allElementOfRange = discreteRange
						.getRangeElements();
				for (DiscreteMeasure element : allElementOfRange) {
					discreteElements[i] = element;
					i++;
				}

			}
			// Verify if the mena is HIGH, LOW or MEDIUM
			if (service.getTrustValue() != null)
				mean = getMeanDiscreteCase(discreteElements, attribute, service
						.getTrustValue());
			if (discreteElements != null && mean != null) {
				stdDev = setStandardDeviation(mean);

				// Returns a value from the distribution with mean and stdDev
				// values.
				int value = generateDiscreteValue(mean, stdDev, attribute
						.getMetric(), discreteElements);
				// find the element at the value position
				DiscreteMeasure discreteMeasue = findDiscreteElement(
						discreteElements, value);
				if (discreteMeasue != null)
					return discreteMeasue;
				else {
					throw new NormalValueGenerationException(
							"The generated value is not a discrete valid value");

				}

			} else
				throw new NormalValueGenerationException(
						"The generated value is not valid");

		} else
			throw new NormalValueGenerationException(
					"The generated value is not valid");
	}

	private static Comparable generateContinuousValue(double mean,
			double standardDeviation, Metric metric) {

		//		
		Comparable value = null;

		do {
			if (metric.getType().equals(MetricType.REAL)) {
				value = (Double) Normal.staticNextDouble(mean,
						standardDeviation);

			} else if (metric.getType().equals(MetricType.INTEGER)) {
				value = (int) Normal.staticNextDouble(mean, standardDeviation);
			}
		} while (!isValid(new ContinueMeasure(value), metric, metric
				.getValidRanges()));

		return value;
	}

	/**
	 * @param mean
	 * @param stdDev
	 * @param metric
	 * @param discreteElements
	 * @return
	 */
	private static int generateDiscreteValue(Double mean, double stdDev,
			Metric metric, DiscreteMeasure[] discreteElements) {
		int value = -1;

		do {
			value = (int) Normal.staticNextDouble(mean, stdDev);
		} while (!(value >= 0 && value < discreteElements.length));

		return value;
	}

	/**
	 * Return true if the value belongs from the validRanges.
	 * 
	 * @param measure
	 * @param metric
	 * @param validRanges
	 * @return
	 */
	private static boolean isValid(IMeasure measure, Metric metric,
			ArrayList<IRange> validRanges) {
		for (int i = 0; i < validRanges.size(); i++) {
			IRange range = validRanges.get(i);
			if (range.containsValue(measure))
				return true;
		}
		return false;
	}

	/**
	 * @param discreteElements
	 * @param value
	 */
	private static DiscreteMeasure findDiscreteElement(
			DiscreteMeasure[] discreteElements, int value) {
		if (value >= 0 && value < discreteElements.length)
			return discreteElements[value];
		else
			return null;
	}

	/**
	 * @param mean
	 * @return
	 */
	private static double setStandardDeviation(Double mean) {
		double stdDev;
		if (mean == 0)
			stdDev = STANDARD_DEVIATION_PERCENTAGE;
		else
			stdDev = mean * STANDARD_DEVIATION_PERCENTAGE;
		return stdDev;
	}

	/**
	 * 
	 */
	private static Double getMeanDiscreteCase(
			DiscreteMeasure[] discreteElements, MeasuredAttribute attribute,
			TrustLevel trustLevel) {
		double mean = 0.0;
		// give an integer number to each discrete value

		switch (trustLevel.getValue()) {
		case 1:
			// LOW
			mean = findElement(discreteElements, attribute.getMetric()
					.getWorstCase());
			break;
		case 2:
			// Medium
			mean = findElement(discreteElements, attribute.getMetric()
					.getMediumCase());
			break;
		case 3:
			// High
			DiscreteMeasure dicreteMeasure = (DiscreteMeasure) attribute
					.getMetric().getBestCase();
			mean = findElement(discreteElements, dicreteMeasure);
			break;
		default:
			break;
		}
		return mean;
	}

	/**
	 * Returns the index of the array where the element
	 * <code>discreteMeasure</code> is or -1 if this element is not present.
	 * 
	 * @param discreteElements
	 * @param discreteMeasure
	 * @return the position of <code>discreteMeasure</code> or -1 if it is not
	 *         found.
	 */
	private static double findElement(DiscreteMeasure[] discreteElements,
			IMeasure discreteMeasure) {
		for (int i = 0; i < discreteElements.length; i++) {
			if (discreteElements[i].equals(discreteMeasure))
				return (double) i;
		}
		return (double) -1;
	}

	/**
	 * 
	 */
	private static Double getMeanContinuosCase(MeasuredAttribute attribute,
			TrustLevel trustValue) {
		Double mean = 0.0;
		Comparable worstCaseComparable = (Comparable) attribute.getMetric()
				.getWorstCase().getValue();
		Comparable bestCaseComparable = (Comparable) attribute.getMetric()
				.getBestCase().getValue();
		Comparable mediumCaseCOmparable = (Comparable) attribute.getMetric()
				.getMediumCase();
		Double worstCase = Double.parseDouble(worstCaseComparable.toString());
		Double bestCase = Double.parseDouble(bestCaseComparable.toString());
		Double mediumCase = Double.parseDouble(mediumCaseCOmparable.toString());

		switch (trustValue.getValue()) {
		case 1:
			// LOW
			mean = worstCase;
			break;
		case 2:
			// Medium
			mean = mediumCase;

			break;
		case 3:
			// High
			mean = bestCase;
			break;

		default:
			break;
		}
		return mean;

	}

	/**
	 * Generate values for the attributes of each services of each source.
	 */
	public static void generateAction(ArrayList<ExternalSource> sourcesList) {
		if (sourcesList != null) {
			for (ExternalSource externalSource : sourcesList) {
				if (externalSource.getMeasuredServices() != null) {
					for (Service service : externalSource.getMeasuredServices()) {
	
						if (service.getMeasuredAttributes() != null) {
							for (MeasuredAttribute attribute : service
									.getMeasuredAttributes()) {
								// Generate a value for the service
								IMeasure measure = null;
								try {
									measure = getGaussianMeasure(
													(ServiceStub) service,
													attribute);
									// setMeasure to the service
									attribute.getMetric().setMeasure(measure);
								} catch (NormalValueGenerationException e) {
									e.printStackTrace();
								}
							}
						}
	
					}
				}
			}
	
		}
	
	}
}
